/* -------------------------------------------------------------------------
 *
 * domains.c
 *	  I/O functions for domain types.
 *
 * The output functions for a domain type are just the same ones provided
 * by its underlying base type.  The input functions, however, must be
 * prepared to apply any constraints defined by the type.  So, we create
 * special input functions that invoke the base type's input function
 * and then check the constraints.
 *
 * The overhead required for constraint checking can be high, since examining
 * the catalogs to discover the constraints for a given domain is not cheap.
 * We have three mechanisms for minimizing this cost:
 *	1.	In a nest of domains, we flatten the checking of all the levels
 *		into just one operation.
 *	2.	We cache the list of constraint items in the FmgrInfo struct
 *		passed by the caller.
 *	3.	If there are CHECK constraints, we cache a standalone ExprContext
 *		to evaluate them in.
 *
 *
 * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/domains.c
 *
 * -------------------------------------------------------------------------
 */
#include "postgres.h"
#include "knl/knl_variable.h"

#include "commands/typecmds.h"
#include "executor/executor.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"

/*
 * structure to cache state across multiple calls
 */
typedef struct DomainIOData {
    Oid domain_type;
    /* Data needed to call base type's input function */
    Oid typiofunc;
    Oid typioparam;
    int32 typtypmod;
    FmgrInfo proc;
    /* List of constraint items to check */
    List* constraint_list;
    /* Context for evaluating CHECK constraints in */
    ExprContext* econtext;
    /* Memory context this cache is in */
    MemoryContext mcxt;
} DomainIOData;

/*
 * domain_state_setup - initialize the cache for a new domain type.
 */
static void domain_state_setup(DomainIOData* my_extra, Oid domainType, bool binary, MemoryContext mcxt)
{
    Oid baseType;
    MemoryContext oldcontext;

    /* Mark cache invalid */
    my_extra->domain_type = InvalidOid;

    /* Find out the base type */
    my_extra->typtypmod = -1;
    baseType = getBaseTypeAndTypmod(domainType, &my_extra->typtypmod);
    if (baseType == domainType)
        ereport(
            ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("type %s is not a domain", format_type_be(domainType))));

    /* Look up underlying I/O function */
    if (binary)
        getTypeBinaryInputInfo(baseType, &my_extra->typiofunc, &my_extra->typioparam);
    else
        getTypeInputInfo(baseType, &my_extra->typiofunc, &my_extra->typioparam);
    fmgr_info_cxt(my_extra->typiofunc, &my_extra->proc, mcxt);

    /* Look up constraints for domain */
    oldcontext = MemoryContextSwitchTo(mcxt);
    my_extra->constraint_list = GetDomainConstraints(domainType);
    MemoryContextSwitchTo(oldcontext);

    /* We don't make an ExprContext until needed */
    my_extra->econtext = NULL;
    my_extra->mcxt = mcxt;

    /* Mark cache valid */
    my_extra->domain_type = domainType;
}

/*
 * domain_check_input - apply the cached checks.
 *
 * This is extremely similar to ExecEvalCoerceToDomain in execQual.c.
 */
static void domain_check_input(Datum value, bool isnull, DomainIOData* my_extra)
{
    ExprContext* econtext = my_extra->econtext;
    ListCell* l = NULL;

    foreach (l, my_extra->constraint_list) {
        DomainConstraintState* con = (DomainConstraintState*)lfirst(l);

        switch (con->constrainttype) {
            case DOM_CONSTRAINT_NOTNULL:
                if (isnull)
                    ereport(ERROR,
                        (errcode(ERRCODE_NOT_NULL_VIOLATION),
                            errmsg("domain %s does not allow null values", format_type_be(my_extra->domain_type))));
                break;
            case DOM_CONSTRAINT_CHECK: {
                /* Make the econtext if we didn't already */
                if (econtext == NULL) {
                    MemoryContext oldcontext;

                    oldcontext = MemoryContextSwitchTo(my_extra->mcxt);
                    econtext = CreateStandaloneExprContext();
                    MemoryContextSwitchTo(oldcontext);
                    my_extra->econtext = econtext;
                }

                /*
                 * Set up value to be returned by CoerceToDomainValue
                 * nodes.  Unlike ExecEvalCoerceToDomain, this econtext
                 * couldn't be shared with anything else, so no need to
                 * save and restore fields.
                 */
                econtext->domainValue_datum = value;
                econtext->domainValue_isNull = isnull;

                bool fit_check = false;
                if (con->check_expr && con->check_expr->is_flt_frame){
                    fit_check = ExecCheckByFlatten(con->check_expr, econtext);
                } else{
                    bool conIsNull = false;
                    Datum conResult = ExecEvalExprSwitchContext(con->check_expr, econtext, &conIsNull,NULL);
                    fit_check =(conIsNull || DatumGetBool(conResult));
                }
                
                if (!fit_check)
                    ereport(ERROR,
                        (errcode(ERRCODE_CHECK_VIOLATION),
                            errmsg("value for domain %s violates check constraint \"%s\"",
                                format_type_be(my_extra->domain_type),
                                con->name)));
                break;
            }
            default:
                ereport(ERROR,
                    (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
                        errmsg("unrecognized constraint type: %d", (int)con->constrainttype)));
                break;
        }
    }

    /*
     * Before exiting, call any shutdown callbacks and reset econtext's
     * per-tuple memory.  This avoids leaking non-memory resources, if
     * anything in the expression(s) has any.
     */
    if (NULL != econtext)
        ReScanExprContext(econtext);
}

/*
 * domain_in		- input routine for any domain type.
 */
Datum domain_in(PG_FUNCTION_ARGS)
{
    char* string = NULL;
    Oid domainType;
    DomainIOData* my_extra = NULL;
    Datum value;

    /*
     * Since domain_in is not strict, we have to check for null inputs. The
     * typioparam argument should never be null in normal system usage, but it
     * could be null in a manual invocation --- if so, just return null.
     */
    if (PG_ARGISNULL(0))
        string = NULL;
    else
        string = PG_GETARG_CSTRING(0);
    if (PG_ARGISNULL(1))
        PG_RETURN_NULL();
    domainType = PG_GETARG_OID(1);

    /*
     * We arrange to look up the needed info just once per series of calls,
     * assuming the domain type doesn't change underneath us.
     */
    my_extra = (DomainIOData*)fcinfo->flinfo->fn_extra;
    if (my_extra == NULL) {
        my_extra = (DomainIOData*)MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, sizeof(DomainIOData));
        domain_state_setup(my_extra, domainType, false, fcinfo->flinfo->fn_mcxt);
        fcinfo->flinfo->fn_extra = (void*)my_extra;
    } else if (my_extra->domain_type != domainType)
        domain_state_setup(my_extra, domainType, false, fcinfo->flinfo->fn_mcxt);

    /*
     * Invoke the base type's typinput procedure to convert the data.
     */
    value = InputFunctionCall(&my_extra->proc, string, my_extra->typioparam, my_extra->typtypmod);

    /*
     * Do the necessary checks to ensure it's a valid domain value.
     */
    domain_check_input(value, (string == NULL), my_extra);

    if (string == NULL)
        PG_RETURN_NULL();
    else
        PG_RETURN_DATUM(value);
}

/*
 * domain_recv		- binary input routine for any domain type.
 */
Datum domain_recv(PG_FUNCTION_ARGS)
{
    StringInfo buf;
    Oid domainType;
    DomainIOData* my_extra = NULL;
    Datum value;

    /*
     * Since domain_recv is not strict, we have to check for null inputs. The
     * typioparam argument should never be null in normal system usage, but it
     * could be null in a manual invocation --- if so, just return null.
     */
    if (PG_ARGISNULL(0))
        buf = NULL;
    else
        buf = (StringInfo)PG_GETARG_POINTER(0);
    if (PG_ARGISNULL(1))
        PG_RETURN_NULL();
    domainType = PG_GETARG_OID(1);

    /*
     * We arrange to look up the needed info just once per series of calls,
     * assuming the domain type doesn't change underneath us.
     */
    my_extra = (DomainIOData*)fcinfo->flinfo->fn_extra;
    if (my_extra == NULL) {
        my_extra = (DomainIOData*)MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, sizeof(DomainIOData));
        domain_state_setup(my_extra, domainType, true, fcinfo->flinfo->fn_mcxt);
        fcinfo->flinfo->fn_extra = (void*)my_extra;
    } else if (my_extra->domain_type != domainType)
        domain_state_setup(my_extra, domainType, true, fcinfo->flinfo->fn_mcxt);

    /*
     * Invoke the base type's typreceive procedure to convert the data.
     */
    value = ReceiveFunctionCall(&my_extra->proc, buf, my_extra->typioparam, my_extra->typtypmod);

    /*
     * Do the necessary checks to ensure it's a valid domain value.
     */
    domain_check_input(value, (buf == NULL), my_extra);

    if (buf == NULL)
        PG_RETURN_NULL();
    else
        PG_RETURN_DATUM(value);
}

/*
 * domain_check - check that a datum satisfies the constraints of a
 * domain.	extra and mcxt can be passed if they are available from,
 * say, a FmgrInfo structure, or they can be NULL, in which case the
 * setup is repeated for each call.
 */
void domain_check(Datum value, bool isnull, Oid domainType, void** extra, MemoryContext mcxt)
{
    DomainIOData* my_extra = NULL;

    if (mcxt == NULL)
        mcxt = CurrentMemoryContext;

    /*
     * We arrange to look up the needed info just once per series of calls,
     * assuming the domain type doesn't change underneath us.
     */
    if (NULL != extra)
        my_extra = (DomainIOData*)*extra;
    if (my_extra == NULL) {
        my_extra = (DomainIOData*)MemoryContextAlloc(mcxt, sizeof(DomainIOData));
        domain_state_setup(my_extra, domainType, true, mcxt);
        if (NULL != extra)
            *extra = (void*)my_extra;
    } else if (my_extra->domain_type != domainType)
        domain_state_setup(my_extra, domainType, true, mcxt);

    /*
     * Do the necessary checks to ensure it's a valid domain value.
     */
    domain_check_input(value, isnull, my_extra);
}
